/* ------------------------------------------
 * Copyright (c) 2016, Synopsys, Inc. All rights reserved.

 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:

 * 1) Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.

 * 2) Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.

 * 3) Neither the name of the Synopsys, Inc., nor the names of its contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * \version 2016.05
 * \date 2014-07-22
 * \author Wayne Ren(Wei.Ren@synopsys.com)
--------------------------------------------- */

/**
 * \defgroup    DEVICE_DW_GPIO  Designware GPIO Driver
 * \ingroup DEVICE_DW
 * \brief   Designware GPIO Driver Implementation
 */

/**
 * \file
 * \brief   designware gpio driver
 * \ingroup DEVICE_DW_GPIO
 * \brief   Designware GPIO driver
 */
#include "inc/embARC_toolchain.h"
#include "inc/embARC_error.h"
#include "inc/arc/arc_exception.h"

#include "device/designware/gpio/dw_gpio.h"

/** check expressions used in DesignWare GPIO driver implementation */
#define DW_GPIO_CHECK_EXP(EXPR, ERROR_CODE)     CHECK_EXP(EXPR, ercd, ERROR_CODE, error_exit)

#ifndef DISABLE_DEVICE_OBJECT_VALID_CHECK
/** valid check of uart info object */
#define VALID_CHK_GPIO_INFO_OBJECT(gpioinfo_obj_ptr)        {               \
            DW_GPIO_CHECK_EXP((gpioinfo_obj_ptr)!=NULL, E_OBJ);         \
            DW_GPIO_CHECK_EXP(((gpioinfo_obj_ptr)->gpio_ctrl)!=NULL, E_OBJ);    \
        }
#endif

/**
 * \defgroup    DEVICE_DW_GPIO_STATIC   DesignWare GPIO Driver Static Functions
 * \ingroup DEVICE_DW_GPIO
 * \brief   Static or inline functions, variables for DesignWare GPIO handle gpio operations,
 *  only used in this file
 * @{
 */
Inline uint32_t dw_gpio_read_ext(DW_GPIO_PORT_PTR port)
{
    return  port->regs->EXT_PORTS[port->no];
}

Inline uint32_t dw_gpio_read_dir(DW_GPIO_PORT_PTR port)
{
    return port->regs->SWPORTS[port->no].DDR;
}

Inline uint32_t dw_gpio_read_dr(DW_GPIO_PORT_PTR port)
{
    return port->regs->SWPORTS[port->no].DR;
}

Inline uint32_t dw_gpio_read_mthd(DW_GPIO_PORT_PTR port)
{
    return port->regs->INTEN;
}

Inline void dw_gpio_int_enable(DW_GPIO_PORT_PTR port, uint32_t bit_mask)
{
    port->regs->INTEN |= bit_mask;
}

Inline void dw_gpio_int_disable(DW_GPIO_PORT_PTR port, uint32_t bit_mask)
{
    port->regs->INTEN &= (~bit_mask);
}

Inline void dw_gpio_int_mask(DW_GPIO_PORT_PTR port, uint32_t bit_mask)
{
    port->regs->INTMASK |= bit_mask;
}

Inline void dw_gpio_int_unmask(DW_GPIO_PORT_PTR port, uint32_t bit_mask)
{
    port->regs->INTMASK &= (~bit_mask);
}

Inline uint32_t dw_gpio_int_read_level(DW_GPIO_PORT_PTR port)
{
    return port->regs->INTTYPE_LEVEL;
}

Inline uint32_t dw_gpio_int_read_polarity(DW_GPIO_PORT_PTR port)
{
    return port->regs->INT_POLARITY;
}

Inline uint32_t dw_gpio_int_read_debounce(DW_GPIO_PORT_PTR port)
{
    return port->regs->DEBOUNCE;
}

Inline uint32_t dw_gpio_int_read_status(DW_GPIO_PORT_PTR port)
{
    return port->regs->INTSTATUS;
}

Inline void dw_gpio_int_clear(DW_GPIO_PORT_PTR port, uint32_t bit_mask)
{
    port->regs->PORTA_EOI = bit_mask;
}

static void dw_gpio_int_write_level(DW_GPIO_PORT_PTR port, uint32_t bit_mask, uint32_t bit_level)
{
    uint32_t reg_val;

    reg_val = port->regs->INTTYPE_LEVEL;
    reg_val &= (~bit_mask);
    bit_level &= bit_mask;
    reg_val |= bit_level;

    port->regs->INTTYPE_LEVEL = reg_val;
}

static void dw_gpio_int_write_polarity(DW_GPIO_PORT_PTR port, uint32_t bit_mask, uint32_t bit_polarity)
{
    uint32_t reg_val;

    reg_val = port->regs->INT_POLARITY;

    reg_val &= (~bit_mask);
    bit_polarity &= bit_mask;
    reg_val |= bit_polarity;

    port->regs->INT_POLARITY = reg_val;
}

static void dw_gpio_int_write_debounce(DW_GPIO_PORT_PTR port, uint32_t bit_mask, uint32_t bit_debounce)
{
    uint32_t reg_val;

    reg_val = port->regs->DEBOUNCE;

    reg_val &= (~bit_mask);
    bit_debounce &= bit_mask;
    reg_val |= bit_debounce;

    port->regs->DEBOUNCE = reg_val;
}

static void dw_gpio_set_int_cfg(DW_GPIO_PORT_PTR port, DEV_GPIO_INT_CFG *int_cfg)
{
    dw_gpio_int_write_level(port, int_cfg->int_bit_mask, int_cfg->int_bit_type);
    dw_gpio_int_write_polarity(port, int_cfg->int_bit_mask, int_cfg->int_bit_polarity);
    dw_gpio_int_write_debounce(port, int_cfg->int_bit_mask, int_cfg->int_bit_debounce);
}

static void dw_gpio_get_int_cfg(DW_GPIO_PORT_PTR port, DEV_GPIO_INT_CFG *int_cfg)
{
    int_cfg->int_bit_type = dw_gpio_int_read_level(port) & int_cfg->int_bit_mask;
    int_cfg->int_bit_polarity = dw_gpio_int_read_polarity(port) & int_cfg->int_bit_mask;
    int_cfg->int_bit_debounce = dw_gpio_int_read_debounce(port) & int_cfg->int_bit_mask;
}

static void dw_gpio_write_dr(DW_GPIO_PORT_PTR port, uint32_t bit_mask, uint32_t val)
{
    uint32_t temp_reg;

    temp_reg = port->regs->SWPORTS[port->no].DR;
    temp_reg &= ~bit_mask;
    val &= bit_mask;
    temp_reg |= val;

    port->regs->SWPORTS[port->no].DR = temp_reg;
}

static void dw_gpio_write_dir(DW_GPIO_PORT_PTR port, uint32_t bit_mask, uint32_t val)
{
    uint32_t temp_reg;

    temp_reg = port->regs->SWPORTS[port->no].DDR;
    temp_reg &= ~bit_mask;
    val &= bit_mask;
    temp_reg |= val;

    port->regs->SWPORTS[port->no].DDR = temp_reg;
}

static uint32_t dw_gpio_read_val(DW_GPIO_PORT_PTR port)
{
    uint32_t val;

    val = dw_gpio_read_ext(port) & (~dw_gpio_read_dir(port));
    val |= dw_gpio_read_dr(port) & dw_gpio_read_dir(port);

    return val;
}

/** @} end of group DEVICE_DW_GPIO_STATIC */

/* interface for DEV_GPIO */
/** Open designware gpio device with specified io direction configuration */
int32_t dw_gpio_open(DEV_GPIO *gpio_obj, uint32_t dir)
{
    int32_t ercd = E_OK;
    DEV_GPIO_INFO_PTR port_info_ptr = &(gpio_obj->gpio_info);

    /* START ERROR CHECK */
    VALID_CHK_GPIO_INFO_OBJECT(port_info_ptr);
    /* END OF ERROR CHECK */

    DW_GPIO_PORT_PTR port = (DW_GPIO_PORT_PTR)(port_info_ptr->gpio_ctrl);
    DW_GPIO_CHECK_EXP(port->no <= DW_GPIO_PORT_D, E_OBJ);

    port_info_ptr->opn_cnt ++;

    if (port_info_ptr->opn_cnt > 1)   /* opened before */
    {
        if (dir == port_info_ptr->direction)   /* direction is the same */
        {
            return E_OK;
        }
        else     /* open with different direction */
        {
            return E_OPNED;
        }
    }

    dw_gpio_write_dir(port, port->valid_bit_mask, dir);

    if (port->no == DW_GPIO_PORT_A)
    {
        dw_gpio_int_clear(port, DW_GPIO_MASK_ALL);
        dw_gpio_int_disable(port, DW_GPIO_MASK_ALL);
        dw_gpio_int_unmask(port, DW_GPIO_MASK_ALL);
        /* install gpio interrupt handler */
        int_handler_install(port->intno, port->int_handler);
        int_disable(port->intno);
        /** Set int type, int polarity and debounce configuration to default settings of device gpio */
        dw_gpio_set_int_cfg(port, (DEV_GPIO_INT_CFG *)(&gpio_int_cfg_default));
        port_info_ptr->method = dw_gpio_read_mthd(port);
    }
    else
    {
        port_info_ptr->method = DEV_GPIO_BITS_MTHD_DEFAULT;
    }

    dw_gpio_write_dr(port, port->valid_bit_mask, 0);

    port_info_ptr->direction = dir;
    port_info_ptr->extra = NULL;

error_exit:
    return ercd;
}

/** Close designware gpio device */
int32_t dw_gpio_close(DEV_GPIO *gpio_obj)
{
    int32_t ercd = E_OK;
    DEV_GPIO_INFO_PTR port_info_ptr = &(gpio_obj->gpio_info);

    /* START ERROR CHECK */
    VALID_CHK_GPIO_INFO_OBJECT(port_info_ptr);
    /* END OF ERROR CHECK */

    DW_GPIO_PORT_PTR port = (DW_GPIO_PORT_PTR)(port_info_ptr->gpio_ctrl);
    DW_GPIO_CHECK_EXP(port->no <= DW_GPIO_PORT_D, E_OBJ);

    DW_GPIO_CHECK_EXP(port_info_ptr->opn_cnt > 0, E_OK);

    port_info_ptr->opn_cnt --;

    if (port_info_ptr->opn_cnt == 0)
    {
        dw_gpio_write_dr(port, port->valid_bit_mask, 0);
        dw_gpio_write_dir(port, port->valid_bit_mask, 0);

        if (port->no == DW_GPIO_PORT_A)
        {
            dw_gpio_int_clear(port, DW_GPIO_MASK_ALL);
            dw_gpio_int_disable(port, DW_GPIO_MASK_ALL);
            int_disable(port->intno);
        }

        port_info_ptr->direction = 0;
        port_info_ptr->method = 0;
        port_info_ptr->extra = NULL;
    }
    else
    {
        ercd = E_OPNED;
    }

error_exit:
    return ercd;
}

/** Read designware gpio device value */
int32_t dw_gpio_read(DEV_GPIO *gpio_obj, uint32_t *val, uint32_t mask)
{
    int32_t ercd = E_OK;
    DEV_GPIO_INFO_PTR port_info_ptr = &(gpio_obj->gpio_info);

    /* START ERROR CHECK */
    VALID_CHK_GPIO_INFO_OBJECT(port_info_ptr);
    /* END OF ERROR CHECK */

    DW_GPIO_PORT_PTR port = (DW_GPIO_PORT_PTR)(port_info_ptr->gpio_ctrl);
    DW_GPIO_CHECK_EXP(port->no <= DW_GPIO_PORT_D, E_OBJ);
    DW_GPIO_CHECK_EXP(port_info_ptr->opn_cnt > 0, E_CLSED);

    DW_GPIO_CHECK_EXP(val != NULL, E_PAR);

    //*val = dw_gpio_read_ext(port) & mask;
    *val = dw_gpio_read_val(port) & mask;

error_exit:
    return ercd;
}

/** Write designware gpio device value */
int32_t dw_gpio_write(DEV_GPIO *gpio_obj, uint32_t val, uint32_t mask)
{
    int32_t ercd = E_OK;
    DEV_GPIO_INFO_PTR port_info_ptr = &(gpio_obj->gpio_info);

    /* START ERROR CHECK */
    VALID_CHK_GPIO_INFO_OBJECT(port_info_ptr);
    /* END OF ERROR CHECK */

    DW_GPIO_PORT_PTR port = (DW_GPIO_PORT_PTR)(port_info_ptr->gpio_ctrl);
    DW_GPIO_CHECK_EXP(port->no <= DW_GPIO_PORT_D, E_OBJ);
    DW_GPIO_CHECK_EXP(port_info_ptr->opn_cnt > 0, E_CLSED);

    dw_gpio_write_dr(port, mask, val);

error_exit:
    return ercd;
}

/** Control designware gpio device */
int32_t dw_gpio_control(DEV_GPIO *gpio_obj, uint32_t ctrl_cmd, void *param)
{
    int32_t ercd = E_OK;
    DEV_GPIO_INFO_PTR port_info_ptr = &(gpio_obj->gpio_info);

    /* START ERROR CHECK */
    VALID_CHK_GPIO_INFO_OBJECT(port_info_ptr);
    /* END OF ERROR CHECK */

    DW_GPIO_PORT_PTR port = (DW_GPIO_PORT_PTR)(port_info_ptr->gpio_ctrl);
    DW_GPIO_CHECK_EXP(port->no <= DW_GPIO_PORT_D, E_OBJ);
    DW_GPIO_CHECK_EXP(port_info_ptr->opn_cnt > 0, E_CLSED);

    uint32_t val32; /** to receive unsigned int value */

    if (ctrl_cmd == GPIO_CMD_SET_BIT_DIR_INPUT)
    {
        val32 = (uint32_t)param;
        dw_gpio_write_dir(port, val32, DW_GPIO_INPUT_ALL);
        port_info_ptr->direction = dw_gpio_read_dir(port);
    }
    else if (ctrl_cmd == GPIO_CMD_SET_BIT_DIR_OUTPUT)
    {
        val32 = (uint32_t)param;
        dw_gpio_write_dir(port, val32, DW_GPIO_OUTPUT_ALL);
        port_info_ptr->direction = dw_gpio_read_dir(port);
    }
    else if (ctrl_cmd == GPIO_CMD_GET_BIT_DIR)
    {
        DW_GPIO_CHECK_EXP((param != NULL) && CHECK_ALIGN_4BYTES(param), E_PAR);
        port_info_ptr->direction = dw_gpio_read_dir(port);
        *((int32_t *)param) = port_info_ptr->direction;
    }
    else
    {
        DW_GPIO_CHECK_EXP(port->no == DW_GPIO_PORT_A, E_NOSPT);
        /* output pin cannot be used as interrupt */
        DEV_GPIO_INT_CFG *gpio_int_cfg;
        DEV_GPIO_BIT_ISR *port_bit_isr;

        switch (ctrl_cmd)
        {
        case GPIO_CMD_SET_BIT_INT_CFG:
            DW_GPIO_CHECK_EXP((param != NULL) && CHECK_ALIGN_4BYTES(param), E_PAR);
            gpio_int_cfg = (DEV_GPIO_INT_CFG *)param;
            dw_gpio_set_int_cfg(port, gpio_int_cfg);
            break;

        case GPIO_CMD_GET_BIT_INT_CFG:
            DW_GPIO_CHECK_EXP((param != NULL) && CHECK_ALIGN_4BYTES(param), E_PAR);
            gpio_int_cfg = (DEV_GPIO_INT_CFG *)param;
            /** read configuration, each bit stands for different configuration */
            dw_gpio_get_int_cfg(port, gpio_int_cfg);
            break;

        case GPIO_CMD_SET_BIT_ISR:
            DW_GPIO_CHECK_EXP((param != NULL) && CHECK_ALIGN_4BYTES(param), E_PAR);
            port_bit_isr = (DEV_GPIO_BIT_ISR *)param;

            if (port_bit_isr->int_bit_ofs < port->gpio_bit_isr->int_bit_max_cnt)
            {
                port->gpio_bit_isr->int_bit_handler_ptr[port_bit_isr->int_bit_ofs] = port_bit_isr->int_bit_handler;
            }
            else
            {
                ercd = E_PAR;
            }

            break;

        case GPIO_CMD_GET_BIT_ISR:
            DW_GPIO_CHECK_EXP((param != NULL) && CHECK_ALIGN_4BYTES(param), E_PAR);
            port_bit_isr = (DEV_GPIO_BIT_ISR *)param;

            if (port_bit_isr->int_bit_ofs < port->gpio_bit_isr->int_bit_max_cnt)
            {
                port_bit_isr->int_bit_handler = port->gpio_bit_isr->int_bit_handler_ptr[port_bit_isr->int_bit_ofs];
            }
            else
            {
                ercd = E_PAR;
            }

            break;

        case GPIO_CMD_ENA_BIT_INT:
            val32 = (uint32_t)param;
            dw_gpio_int_enable(port, val32);
            port_info_ptr->method = dw_gpio_read_mthd(port);

            if (port_info_ptr->method)
            {
                int_enable(port->intno);
            }

            break;

        case GPIO_CMD_DIS_BIT_INT:
            val32 = (uint32_t)param;
            dw_gpio_int_disable(port, val32);
            port_info_ptr->method = dw_gpio_read_mthd(port);

            if (port_info_ptr->method == 0)
            {
                int_disable(port->intno);
            }

            break;

        case GPIO_CMD_GET_BIT_MTHD:
            DW_GPIO_CHECK_EXP((param != NULL) && CHECK_ALIGN_4BYTES(param), E_PAR);
            port_info_ptr->method = dw_gpio_read_mthd(port);
            *((int32_t *)param) = port_info_ptr->method;
            break;

        default:
            ercd = E_NOSPT;
            break;
        }
    }

error_exit:
    return ercd;
}

/** designware gpio interrupt process */
int32_t dw_gpio_isr_handler(DEV_GPIO *gpio_obj, void *ptr)
{
    int32_t ercd = E_OK;
    DEV_GPIO_INFO_PTR port_info_ptr = &(gpio_obj->gpio_info);

    /* START ERROR CHECK */
    VALID_CHK_GPIO_INFO_OBJECT(port_info_ptr);
    /* END OF ERROR CHECK */

    DW_GPIO_PORT_PTR port = (DW_GPIO_PORT_PTR)(port_info_ptr->gpio_ctrl);
    DW_GPIO_CHECK_EXP(port->no == DW_GPIO_PORT_A, E_NOSPT);

    uint32_t i, gpio_bit_isr_state;
    uint32_t max_int_bit_count = 0;

    /** read interrupt status */
    gpio_bit_isr_state = dw_gpio_int_read_status(port);

    if (port->gpio_bit_isr)
    {
        max_int_bit_count = (port->gpio_bit_isr->int_bit_max_cnt);
    }
    else
    {
        dw_gpio_int_clear(port, gpio_bit_isr_state);
    }

    for (i = 0; i < max_int_bit_count; i++)
    {
        if (gpio_bit_isr_state & (1 << i))
        {
            /* this bit interrupt enabled */
            if (port->gpio_bit_isr->int_bit_handler_ptr[i])
            {
                port->gpio_bit_isr->int_bit_handler_ptr[i](gpio_obj);
            }

            dw_gpio_int_clear(port, (1 << i)); /** clear this bit interrupt */
        }
    }

error_exit:
    return ercd;
}
